Разработка расширенных хранимых процедур.

Для программирования расширенных хранимых процедур Microsoft предоставляет ODS (Open Data Service) API – набор макросов и функций, используемых для построения серверных приложений позволяющих расширить функциональность MS SQL Server 2000.

Расширенные хранимые процедуры - это обычные функции написанные на С/C++ с применением ODS API и WIN32 API, оформленные в виде библиотеки динамической компоновки (dll) и призванные, как я уже говорил, расширять функциональность SQL сервера. ODS API предоставляет разработчику богатый набор функций позволяющих передавать данные клиенту, полученные от любых внешних источников данных (data source) в виде обычных наборов записей (record set). Так же, extended stored procedure может возвращать значения через переданный ей параметр (OUTPUT parametr).

Как работают расширенные хранимые процедуры.
1. Когда клиентское приложение вызывает расширенную хранимую процедуру, запрос передаётся в TDS формате через сетевую библиотеку Net-Libraries и Open Data Service ядру MS SQL SERVER.
2. SQL Sever находит dll – библиотеку ассоциированную с именем расширенной хранимой процедуры и загружает её в свой контекст, если она не была загружена туда ранее, и
вызывает расширенную хранимую процедуру, реализованную как функцию внутри dll.
3. Расширенная хранимая процедура выполняет на сервере необходимые ей действия и передаёт набор результатов клиентскому приложению, используя сервис предоставляемый ODS API.

Особенности расширенных хранимых процедур.
- Расширенные хранимые процедуры - это функции выполняющиеся в адресном пространстве MS SQL Server и в контексте безопасности учётной записи под которой запущена служба MS SQL Server;
- После того, как dll библиотека с расширенными хранимыми процедурами была загружена в память, она остаётся там до тех пор, пока SQL Server не будет остановлен, или пока администратор не выгрузит её принудительно, используя команду :
DBCC DLL_name (FREE).
- Расширенная хранимая процедура запускается на выполнение так же, как и обычная хранимая процедура:
EXECUTE xp_extendedProcName @param1, @param2 OUTPUT
@param1 – входной параметр
@param2 – входной/выходной параметр

Внимание!
Так как расширенные хранимые процедуры выполняются в адресном пространстве процесса службы MS SQL Server, любые критические ошибки, возникающие в их работе, могут вывести из строя ядро сервера, поэтому рекомендуется тщательно протестировать Вашу DLL перед установкой на рабочий сервер.
Создание расширенных хранимых процедур.
Расширенная хранимая процедура – эта функция имеющая следующий прототип:

SRVRETCODE xp_extendedProcName (SRVPROC * pSrvProc);

Параметр pSrvProc – указатель на SRVPROC структуру, которая является описателем (handle) каждого конкретного клиентского подключения. Поля этой структуры недокументированны и содеражат информацию, которую библиотека ODS использует для управления коммуникацией и данными между серверным приложением (Open Data Services server application) и клиентом. В любом случае, Вам не потребуется обращаться к этой структуре и тем более нельзя модифицоравать её. Этот параметр требуется указывать при вызове любой функции ODS API, поэтому в дальнейшем я небуду останавливаться на его описании.
Использование префикса xp_ необязательно, однако существует соглашение начинать имя расширенной хранимой процедуры именно так, чтобы подчеркнуть отличие от обычной хранимой процедуры, имена которых, как Вы знаете, принято начинать с префикса sp_.
Так же следует помнить, что имена расширенных хранимых процедур чувствительны к регистру. Не забывайте об этом, когда будете вызвать расширенную хранимую процедуру, иначе вместо ожидаемого результата, Вы получите сообщение об ошибке.
Если Вам необходимо написать код инициализации/деинициализации dll, используйте для этого стандартную функцию DllMain(). Если у Вас нет такой необходимости, и вы не хотите писать DLLMain(), то компилятор соберёт свою версию функции DLLMain(), которая ничего не делает, а просто возвращает TRUE. Все функции, вызываемые из dll (т.е. расширенные хранимые процедуры) должны быть объявлены, как экспортируемые. Если Вы пишете на MS Visual C++ используйте директиву __declspec(dllexport). Если Ваш компилятор не поддерживает эту директиву, опишите экспортируемую функцию в секции EXPORTS в DEF файле.
Итак, для создания проекта, нам понадобятся следующие файлы:

- Srv.h – заголовочный файл, содержит описание функций и макросов ODS API;
- Opends60.lib – файл импорта библиотеки Opends60.dll, которая и реализует весь сервис предоставляемый ODS API.

Microsoft настоятельно рекомендует, чтобы все DLL библиотеки реализующие расширенные хранимые процедуры экспортировали функцию:

__declspec(dllexport) ULONG __GetXpVersion()
{
return ODS_VERSION;
}

Когда MS SQL Server загружает DLL c extended stored procedure, он первым делом вызывает эту функцию, чтобы получить информацию о версии используемой библиотеки.

Для написания своей первой extended stored procedure, Вам понадобится установить на свой компьютер:

- MS SQL Server 2000 любой редакции (у меня стоит Personal Edition). В процесе инсталляции обязательно выберите опцию source sample
- MS Visual C++ (я использовал версию 7.0 ), но точно знаю подойдёт и 6.0

Установка SQL Server -a нужна для тестирования и отладки Вашей DLL. Возможна и отладка по сети, но я этого никогда не делал, и поэтому установил всё на свой локальный диск. В поставку Microsoft Visual C++ 7.0 редакции Interprise Edition входит мастер – Extended Stored Procedure DLL Wizard. В принципе, ничего сверх естественного он не делает, а только генерирует заготовку – шаблон расширенной хранимой процедуры. Если Вам нравятся мастера, можете использовать его. Я же предпочитаю делать всё ручками, и поэтому не буду рассматривать этот случай.

Теперь к делу:
- Запустите Visual C++ и создайте новый проект - Win32 Dynamic Link Library.
- Включите в проект заголовочный файл - #include ;
- Зайдите в меню Tools => Options и добавьте пути поиска include и library файлов. Если , при установке MS SQL Server, Вы ничего не меняли, то задайте:

- C:Program FilesMicrosoft SQL Server80ToolsDevToolsInclude для заголовочных файлов;
- C:Program FilesMicrosoft SQL Server80ToolsDevToolsLib для библиотечных файлов.
- Укажите имя библиотечного файла opends60.lib в опциях линкера.

На этом подготовительный этап закончен, можно приступать к написанию своей первой extended stored procedure.

Постановка задачи.
Прежде чем приступать к программированию, необходимо чётко представлять с чего начать, какой должен быть конечный результат, и каким способом его добиться. Итак, вот нам техническое задание:

« Разработать расширенную хранимую процедуру для MS SQL Server 2000, которая получает полный список пользователей зарегистрированных в домене, и возвращает его клиенту в виде стандартного набора записей (record set). В качестве первого входного параметра функция получает имя сервера содержащего базу данных каталога (Active Directory), т.е имя контролера домена. Если этот параметр равен NULL, тогда необходимо передать клиенту список локальных групп. Второй параметр будет использоваться extended stored procedure для возварата значения – результата успешной/неуспешной работы (OUTPUT параметр). Если, расширенная хранимая процедура выполнена успешно, тогда необходимо передать количество записей возвращённых в клиентский record set , если в процессе работы не удалось получить требуемую информацию, значение второго параметра необходимо установить в -1, как признак неуспешного завершения. »

«Условный прототип» расширенной хранимой процедуры следующий:

xp_GetUserList(@NameServer varchar[15], @CountRec int OUTPUT);

А вот шаблон расширенной хранимой процедуры, который нам предстоит наполнить содержанием:

#include
#include
#define XP_NOERROR 0
#define XP_ERROR -1


__declspec(dllexport) SERVRETCODE xp_GetGroupList(SRVPROC* pSrvProc)
{

//Проверка кол-ва переданных параметров

//Проверка типа переданных параметров

//Проверка, является ли параметр 2 OUTPUT параметром

//Проверка, имеет ли параметр 2 достаточную длину для сохранения значения

//Получение входных параметров

//Получение списка пользователей

// Посылка полученных данных клиенту в виде стандартного набора записей (record set)

//Установка значения OUTPUT параметра

return (XP_NOERROR);
}


Работа с входными параметрами

В этой главе я не хочу рассеивать Ваше внимание на посторонних вещах, а хочу сосредоточить его на работе с переданными в расширенную хранимую процедуру параметрами. Поэтуму мы несколько упростим наше техническое задание и разработаем тольку ту его часть, которая работает с входными параметрами. Но сначал не много теории …

Первое действие, которое должна выполнить наша exteneded stored procedure , - получить параметры, которые были переданы ей при вызове. Следуя приведённому выше алгоритму нам необходимо выполнить следующие действия:

- Определить кол-во переданных параметров;
- Убедится, что переданные параметры имеют верный тип данных;
- Убедиться, что указанный OUTPUT параметр имеет достаточную длину, для сохранения в нём значения возвращаемого нашей extended stored procedure.
- Получить переданные параметры;
- Установить значения выходного параметра как результат успешного/неуспешного завершения работы extended stored procedure .

Теперь рассмотрим подробно каждый пункт:

Определение количества переданных в расширенную хранимую процедуру параметров

Для получения количества переданных параметров необходимо использовать функцию:

int srv_rpcparams ( SRV_PROC * srvproc );

При успешном завершении функция возвращает количество переданных в расширенную хранимую процедуру параметров. Если extended stored procedure была вызвана без параметров - srv_rpcparams ввернёт -1. Параметры могут быть переданы по имени или по позиции (unnamed). В любом случае, нельзя смешивать эти два способа. Попытка передачи в функцию входных параметров по имени и по позиции одновременно - приведёт к возникновению ошибки, и srv_rpcparams вернёт 0 .
Определение типа данных и длины переданых параметров
Для получения информации о типе и длине переданных параметров Microsoft рекомендует использовать функцию srv_paramifo. Эта универсальная функция заменяет вызовы srv_paramtype, srv_paramlen, srv_parammaxlen, которые теперь считаются устаревшими. Вот её прототип:

int srv_paraminfo (
SRV_PROC * srvproc,
int n,
BYTE * pbType,
ULONG* pcbMaxLen,
ULONG * pcbActualLen,
BYTE * pbData,
BOOL * pfNull );

pByte – указатель на переменную получающую информацию о типе входного параметра;
pbType – задаёт порядковый номер параметра. Номер первого параметра начинается с 1.
pcbMaxLen – указатель на переменную, в которую функция заносит максимальное значение длины параметра. Это значение обусловлено конкретным типом данных переданного параметра, его мы и будем использовать, чтобы убедиться втом, что OUTPUT параметр имеет достаточную длину для сохранения передаваемых данных.
pcbActualLen – указатель на реальную длину параметра переданного в расширенную хранимую процедуру при вызове. Если передаваемый параметр имеет нулевую длину, а флаг pfNull устанавлен в FALSE то (* pcbActualLen) ==0.
pbData - указатель на буфер, память для которого должна быть выделена перед вызовом srv_paraminfo. В этом буфере функция размещает полученные от extended stored procedure входные параметры. Размер буфера в байтах равен значению pcbMaxLen. Если этот параметр установлен в NULL, данные в буфер не записываются, но функция корректно возвращает значения *pbType, *pcbMaxLen, *pcbActualLen, *pfNull. Поэтому вызывать srv_paraminfo нужно дважды: сначала с pbData=NULL, потом, выделив необходимый размер памяти под буфер равный pcbActualLen, вызвать srv_paraminfo второй раз, передав в pbData указатель на выделенный блок памяти.
pfNull – указатель на NULL-флаг. srv_paraminfo устанавливает его в TRUE, если значение входного параметра равно NULL.

Проверка, является ли второй параметр OUTPUT параметром.

Функция srv_paramstatus() предназначена для определения статуса переданного параметра:
int srv_paramstatus (
SRV_PROC * srvproc,
int n
);
n – номер параметра переданного в расширенную хранимую процедуру при вызове. Напомню: параметры всегда нумеруются с 1.
Для возврата значения, srv_paramstatus использует нулевой бит. Если он установлен в 1 – переданный параметр является OUTPUT параметром, если в 0 – обычным параметром, переданным по значению. Если, exteneded stored procedure была вызвана без параметров, функция вернёт -1.
Установка значения выходного параметра.
Выходному параметру, переданному в расширеную хранимую можно передать значение используя функцию srv_paramsetoutput. Эта новая функция заменяет вызов функции srv_paramset, которая теперь считается устаревашай, т.к. не поддерживает новые типы данных введённые в ODS API и данные нулевой длины.
int srv_paramsetoutput (
SRV_PROC * srvproc,
int n,
BYTE * pbData,
ULONG cbLen,
BOOL fNull
);

n – порядковый номер параметра, которому будет присвоено новое значение. Это должен быть OUTPUT параметр.
pbData – указатель на буфер с данными, которые будут посланы клиенту для установки значения выходного параметра.
cbLen – длина буфера посылаемых данных. Если тип данных переданного OUTPUT параметра определяет данные постоянной длины и не разрешает хранение значения NULL (например SRVBIT или SRVINT1), то функция игнорирует параметр cbLen. Значение cbLen=0 указывает на данные нулевой длины, при этом парметр fNull должен быть установлен в FALSE.
fNull – установите этот его в TRUE, если возвращаемому параметру необходимо присвоить значение NULL, при этом значение cbLen должно быть равно 0, иначе функция завершится с ошибкой. Во всех остальных случаях fNull=FALSE.
В случае успешного завершения функция возвращает SUCCEED. Если возвращаемое значение равно FAIL, значит вызов был неудачным. Всё просто и понятно …
Теперь мы достаточно знаем, для того чтобы написать свою первую расширенную хранимую процедуру, которая будет возвращать значение через переданный ей параметр.Пусть, по сложившейся традиции, это будет строка – «Hello world!» Отладочну версию примера можно скачать здесь.

#include

#define XP_NOERROR 0
#define XP_ERROR 1

#define MAX_SERVER_ERROR 20000
#define XP_HELLO_ERROR MAX_SERVER_ERROR+1

void printError (SRV_PROC*, CHAR*);

#ifdef __cplusplus
extern "C" {
#endif

SRVRETCODE __declspec(dllexport) xp_helloworld(SRV_PROC* pSrvProc);

#ifdef __cplusplus
}
#endif

SRVRETCODE xp_helloworld(SRV_PROC* pSrvProc)
{
char szText[13] = "Hello World!";
BYTE bType;
ULONG cbMaxLen;
ULONG cbActualLen;
BOOL fNull;

/* Определение количества переданных в расширенную хранимую
процедуру параметров */
if (srv_rpcparams(pSrvProc) != 1)
{
printError(pSrvProc, "Не верное количество параметров!");
return (XP_ERROR);
}

/* Получение информации о типе данных и длине переданых параметров */
if (srv_paraminfo(pSrvProc, 1, &bType, &cbMaxLen,
&cbActualLen, NULL, &fNull) == FAIL)
{
printError (pSrvProc,
"Не удаётся получить информацию о входных параметрах ...");
return (XP_ERROR);
}

/* Проверяем, является ли переданный параметр OUTPUT параметром */
if ((srv_paramstatus(pSrvProc, 1) & SRV_PARAMRETURN) == FAIL)
{
printError (pSrvProc,
"Переданный параметр не является OUTPUT параметром!");
return (XP_ERROR);
}

/* Проверяем тип данных переданного параметра */
if (bType != SRVBIGVARCHAR && bType != SRVBIGCHAR)
{
printError (pSrvProc, "Не верный тип переданного параметра!");
return (XP_ERROR);
}

/* Убедимся, что переданный параметр имеет достаточную длину для сохранения возвращаемой строки */
if (cbMaxLen < strlen(szText))
{
printError (pSrvProc,
"Передан параметр не достаточной длины для сохранения n возвращаемой строки!");
return (XP_ERROR);
}

/* Устаналиваем значение OUTPUT параметра */
if (FAIL == srv_paramsetoutput(pSrvProc, 1, (BYTE*)szText, 13, FALSE))
{
printError (pSrvProc,
"Не могу установить значение OUTPUT параметра...");
return (XP_ERROR);
}

srv_senddone(pSrvProc, (SRV_DONE_COUNT | SRV_DONE_MORE), 0, 1);
return (XP_NOERROR);
}

void printError (SRV_PROC *pSrvProc, CHAR* szErrorMsg)
{
srv_sendmsg(pSrvProc, SRV_MSG_ERROR, XP_HELLO_ERROR, SRV_INFO, 1,
NULL, 0, 0, szErrorMsg,SRV_NULLTERM);

srv_senddone(pSrvProc, (SRV_DONE_ERROR | SRV_DONE_MORE), 0, 0);
}

Не рассмотренными остались функции srv_sendmsg и srv_senddone. Функция srv_sendmsg используется для посылки сообщений клиенту. Вот её прототип:

int srv_sendmsg (
SRV_PROC * srvproc,
int msgtype,
DBINT msgnum,
DBTINYINT class,
DBTINYINT state,
DBCHAR * rpcname,
int rpcnamelen,
DBUSMALLINT linenum,
DBCHAR * message,
int msglen
);

msgtype – определяет тип посылаемого клиенту сообщения. Константа SRV_MSG_INFO обозначает информационное сообщение, а SRV_MSG_ERROR сообщение об ошибке;
msgnum – номер сообщения;
class - степень «тяжести» возникшей ошибки. Информационные сообщения имеют значение степени «тяжести» меньшее или равное 10;
state – номер состояния ошибки для текущего сообщения. Этот параметр предоставляет информацию о контексте возникшей ошибки. Допустимые значения лежат в диапазоне от 0 до 127;
rpcname – в настоящее время не используется;
rpcnamelen - в настоящее время не используется;
linenum – здесь можно указать номер строки исходного кода. По этому значению, в последствие будет легко установить в каком месте возникла ошибка. Если Вы не хотите использовать эту возможность, тогда установите linenum в 0;
message – указатель на строку посылаемую клиенту;
msglen – определяет длину в байтах строки сообщения. Если это строка заканчивается нулевым символом, то значение этого параметра можно установить равным SRV_NULLTERM.
Возвращаемыме значения:
- в случае успеха SUCCEED
- при неудаче FAIL.

В процессе работы расширенная хранимая процедура должна регулярно сообщать клиентскому приложению свой статус, т.е. посылать сообщения о выполненных действиях. Для этого и предназначена функция srv_senddone:

int srv_senddone (
SRV_PROC * srvproc,
DBUSMALLINT status,
DBUSMALLINT info,
DBINT count
);
status
– статус флаг. Значение этого параметра можно задавать использую логические операторы AND и OR для комбинирования констант приведённых в таблице:
Status flag Описание
SRV_DONE_FINAL Текущий набор результатов является окончательным;
SRV_DONE_MORE Текущий набор результатов не является окончательным – следует ожидать очердную порцию данных;
SRV_DONE_COUNT Параметр count содержит верное значение
SRV_DONE_ERROR Используется для уведомления о возникновении ошибок и немедленном завершении.
into – зарезервирован, необходимо установить в 0.
count – количество результирующих наборов данных посылаемых клиенту. Если флаг status установлен в SRV_DONE_COUNT, то count должен содержать правильное количество посылаемый клиенту наборв записей.
Возвращаемыме значения:
- в случае успеха SUCCEED
- при неудаче FAIL.

Установка расширенных хранимых процедур на MS SQL Server 2000
1. Скопируйте dll библиотеку с расширенной хранимой процедурой в каталог binn на машине с установленным MS SQL Server. У меня этот путь следующий: C:Program FilesMicrosoft SQL ServerMSSQLBinn;
2. Зарегистрирйте расширенную хранимую процедуру на серверt выполнив следующий скрипт:
USE Master
EXECUTE SP_ADDEXTENDEDPROC ‘xp_helloworld’, ‘xp_helloworld.dll’
Протестируйте работу xp_helloworld, выполнив такой скрипт:
DECLARE @Param varchar(33)
EXECUTE xp_helloworld @Param OUTPUT
SELECT @Param AS OUTPUT_Param
Заключение
На этом первая часть моей статьи закончена. Теперь я уверен – Вы готовы справиться с нашим техническим заданием на все 100%. В следующей статье Вы узнаете:
- Типы данных определённые в ODS API;
- Особенности отладки расширенных хранимых процдур;
- Как формировать recordset-ы и передавать их клиентскому приложению;
- Чстично мы рассмотрим функции Active Directory Network Manegment API необходимые для получения списка доменных пользователей;
- Создадим готовый проект (реализуем наше техническое задание)
Надеюсь - до скорой встречи!

PS: файлы примера для статьи качать здесьдля студии 7.0

Версия для студии VC 6.0 здесь


Типы данных определённые в ODS API.

Ниже в таблице приведены типы данных определённые в ODS API и их соответствие стандартным типам данных языка SQL поддерживаемых в MS SQL Server 2000. Думаю, что здесь всё должно быть понятно:

Тип данных определённый в ODS API
Тип данных в MS SQL Server 2000 Описание

SRVBIGBINARY
binary Бинарные данные. Максимальная размер 8000 байт.

SRVBIGCHAR
char Строка символов. Максимальный размер 8000 байт.

SRVBIGVARBINARY
varbinary Бинарные данные с переменным размером. 0 – 8000 байт.

SRVBIGVARCHAR
varchar Строка символов с переменным размером 0 – 8000 байт.

SRVBINARY
binary Бинарный тип данных

SRVBIT
Bit Бит

SRVBITN
bit null Бит разрешающие хранение значения NULL.

SRVCHAR
char Строка символов. Максимальный размер 8000 байт.

SRVDATETIME
datetime Тип данных для хранения значения даты/времени в 8 байтовом формате.

SRVDATETIM4
smalldatetime Тип данных для хранения значения даты/времени в 4 байтовом формате.

SRVDATETIMN
datetime null smalldatetime или datetime тип данных разрешающий хранение значения NULL.

SRVDECIMAL
decimal Десятичные числа в диапазоне 0 – 99. Размер 2 – 17 байт (в зависимости от количества цифр).

SRVDECIMALN
decimal null Десятичные числа в диапазоне 0 – 99. Разрешает хранение значения NULL.

SRVFLT4
real Десятичные числа в диапазоне -3,40Е+38 – 3,40Е+38. Размер 4 байта.

SRVFLT8
float Десятичные числа в диапазоне -1,79Е+308 – 1,79Е+308. . Размер 4 или 8 байт (в зависимости от величины мантиссы).

SRVFLTN
real | float null real или float тип данных разрешающий хранение значения NULL.

SRVIMAGE
image Бинарные данные. Максимальный размер 2Гб.

SRVINT1
tinyint Целые числа в диапазоне 0 – 255. Размер 1 байт.

SRVINT2
smallint Целые числа в диапазоне -32768 – 32767. Размер 2 байта.

SRVINT4
Int Целые числа в диапазоне -2147483648 – 2147483647. Размер 4 байта.

SRVINTN
tinyint | smallint | int null tinyint, smallint, или int тип данных разрешающий хранения значения NULL.

SRVMONEY4
smallmoney Денежный тип данных. Обеспечивает хранение до 4-ёх цифр после десятичной точки. Диапазон значений: -214748,3648 - +214748,3647. Размер 4 байта.

SRVMONEY
money Денежный тип данных. Обеспечивает хранение до 4-ёх цифр после десятичной точки. Диапазон значений:-922337203685477,5808 - +922337203685477,5807.Размер 8 байт.

SRVMONEYN
money | smallmoney null smallmoney или money разрешающий хранение значения NULL.

SRVNCHAR
nchar Строка символов в формате UNICODE. Размер 8000 байт (4000 символов).

SRVNTEXT
ntext Обеспечивает хранение текстовых блоков данных в формате UNICODE длиной до 1073741823 символов.

SRVNUMERIC
numeric Десятичные числа в диапазоне 0 – 99. Размер 2 – 17 байт (в зависимости от количества цифр).

SRVNUMERICN
numeric null numeric тип данных разрешающий хранение значения NULL.

SRVNVARCHAR
nvarchar Строка символов переменной длины в формате UNICODE. Максимальный размер 8000 байт (4000 символов).

SRVTEXT
text Обеспечивает хранение текстовых блоков данных длиной до 2147483647 символов.

SRVVARCHAR
varchar Строка символов переменной длины. Размер 0 – 8000 байт.

Для преобразования типов данных Вы можете использовать функцию srv_convert, вот её прототип:
int srv_convert (
SRV_PROC * srvproc,
int srctype,
void * src,
DBINT srclen,
int desttype,
void * dest,
DBINT destlen
)
srvproc
– определяет тип исходных данных, которые будут конвертированы. Этот параметр может описывать любой тип данных определённый в ODS API;
src – указатель на буфер с исходными данными, которые будут конвертированы;
srclen – длина в байтах буфера с исходными данными подлежащими конвертированию. Если этот параметр равен нулю, функция помещает значение NULL в параметр dest. Если srclen не равен нулю, то он будет игнорироваться для типов данных постоянной длины. В этом случае предполагается, что исходные данные допускают хранение значения NULL Для типа SRVCHAR допускается указывать значение -1, обозначающее строку с завершающим нулевым символом.
destype – определяет тип данных в которые будут конвертированы исходные данные. Этот параметр может описывать любой тип данных определённый в ODS API;
dest – указатель на буфер, который получает преобразованные данные. Если этот параметр равен NULL, функция вызывает обработчик ошибки и возвращает -1 ;
destlen – длина в байтах буфера получающего преобразованные данные. Этот параметр игнорируется для типов данных постоянной длины. Если данные преобразуются к типу SRVCHAR, значение destlen должно определять полную длину буфера получающего данные. Допускается указывать значение -1 для строк с завершающим нулём.

Возвращаемое значение:
Если конвертирование успешно – функция возвращает длину в байтах преобразованных данных. Если функция встречает не поддерживаемый для преобразования тип данных – вызывается обработчик ошибки определённый пользователем, устанавливается глобальный номер ошибки и функция возращает -1.

В MSDN описаны допустимые в ODS преобразования типов данных, которое я привожу и Вам:


Передача наборов записей клиентскому приложению.

В этой главе объясняется, как расширенная хранимая процедура может формировать наборы данных и возвращать их клиентскому приложению. Здесь я предлагаю Вам написать расширенную хранимую процедуру xp_hello, которая возвращает строку «Hello world!», но уже в клиентский набор данных. Но сначала, как обычно, разберём несколько функций …
Перед отправкой записи клиентскому приложению расширенная хранимая процедура должна выполнить несколько подготовительных действий, а именно описать:
- имя и порядковый номер каждого столбца в строке;
- тип исходных данных , и тип данных которые будут посланы в клиентский набор записей;
- связать адрес буфера отправляемых данных с номером столбца.
Для этого и предназначена функция srv_describe:

int srv_describe (
SRV_PROC * srvproc,
int colnumber,
DBCHAR * column_name,
int namelen,
DBINT desttype,
DBINT destlen,
DBINT srctype,
DBINT srclen,
void * srcdata
)

colnumber – порядковый номер столбца. Номера столбцов начинаются с 1;
column_name – указатель на буфер содержащий имя столбца. Этот параметр можно установить в NULL, так как имя столбца не является обязательным атрибутом;
namelen – длина в байтах имени столбца. Если строка с именем столбца заканчивается нулевым символом, значения этого параметра можно установить равным константе SRV_NULLTERM.

------------- аргументы влияющие на посылаемые клиенту данные (destination data) --------------

destype – указывает тип данных элемента записи (т.е ячейки образуемой на пересечении строки и столбца) посылаемых клиенту. Этот тот параметр должен быть указан даже если параметр srcdata = NULL.
destlen – длина в байтах данных, которые будут посланы клиенту. Для типов данных фиксированной длины не разрешающих хранение значения NULL, этот параметр игнорируется. Для типов данных переменной длины и типов данных фиксированной длинны, которые разрешают хранения значения NULL, параметр destlen задаёт максимальную длину передаваемых клиентскому приложению данных.

----------------------- аргументы влияющие на исходные данные (source data) -------------------------

srctype – описывает тип исходных данных.
srclen – указывает длину в байтах исходных (source) данных . Это значение игнорируется для типов данных фиксированной длины.
srcdata – указатель на буфер с исходными данными, которые функция сопоставляет с номером столбца (задаёт параметр colnumber) .Память выделенная под буфер не должна быть освобождена перед вызовом функции srv_sendrow.(см. ниже)

Возвращаемое значение:
В случае успешного завершения - номер столбца с которым сопоставлены данные;
Если возникли ошибки – функция вернёт 0.

Перед отправкой данных нам необходимо описать номера всех столбцов клиентского recordset-а и связать их с адресом буфера, который содержит подготовленные для отправки клиенту данные. После этого нужно вызвать функцию srv_sendrow которая передаст данные в клиентское приложение. Вот её прототип:
int srv_sendrow ( SRV_PROC * srvproc );
Этот фрагмент кода формирует набор записей из одной строки и одного столбца и передаёт его клиентскому приложению:
char szText[15] = "Hello World!";
srv_describe(pSrvProc, 1, "Column 1", SRV_NULLTERM, SRVBIGCHAR, 15, SRVBIGCHAR, strlen(szText), szText);
srv_sendrow(pSrvProc);
srv_senddone(pSrvProc, (SRV_DONE_COUNT | SRV_DONE_MORE), 0, 1);

Добавьте этот фрагмент в пример xp_helloworld из первой части статьи, и Вы получите расширенную хранимую процедуру, которая возвращает строку «Hello world!» в клиентский набор данных! Но не радуйтесь, ибо это ИМХО самый простой случай! А как быть, если необходимо динамически формировать наборы данных? Если размер передаваемых данных заранее не известен? Каждый раз заново описывать все столбцы при помощи функции srv_describe? Ответ на все три вопроса один – НЕТ!
Для того, чтобы связать нужный столбец с новыми данными (буфер для которых должен быть к этому моменту уже выделен) необходимо вызвать функцию srv_setcoldata. Вот её прототип:
int srv_setcoldata (
SRV_PROC * srvproc,
int column,
void * data
)

column – номер столбца с которым будут сопоставлены данные. Столбцы нумеруются с единицы.
data – указатель на буфер с данными. Память, выделенная для этого буфера не должна быть освобождена, пока не будет завершён вызов функции srv_sendrow.
Если для отправки данных Вы выделили в памяти буфер, размер которого остаётся постоянным, а меняется только его содержимое (т.е. сами данные), то вызова функции srv_setcoldata будет достаточно, чтобы сопоставить новые данные с номером нужного столбца. Но так, скорее всего, бывает редко … Ели размер отправляемых данных изменяется вместе с их содержимым, нам придется (каждый раз перед отправкой строки данных клиенту!), указывать новую длину отправляемого буфера. И делать это нужно обязательно перед тем, как будет вызвана srv_sendrow ! Для этого предназначена функция srv_setcollen:
int srv_setcollen (
SRV_PROC * srvproc,
int column,
int len
)

column – номер столбца для которого указывается новое значение длины данных.
len – задаёт длину в байтах столбца с данными, которые будут посланы клиенту.
Следующий фрагмент кода динамический формирует набор данных, состоящий из четырёх столбцов и трёх строк разной длины и передаёт его клиентскому приложению:
struct UserInfo{
int id;
char *Name, *FullName, *Comment;
};

UserInfo Buff[3] = {1001, "Вася", "Вася Пупкин", "Лётчик-испытатель",
1002, "Николай", "Николай Иванов","Архитектор",
1003, "Петя", "Петя Булочкин", "Дизайнер"};


/*Здесь вместо указателя на буфер с данными задаём NULL, а длину буфера равной 0 */
srv_describe(pSrvProc, 1, "ID", SRV_NULLTERM, SRVINT4, 4, SRVINT4, 4, NULL);
srv_describe(pSrvProc, 2, "Name", SRV_NULLTERM, SRVBIGCHAR, 8000, SRVBIGCHAR, 0, NULL);
srv_describe(pSrvProc, 3, "Full name", SRV_NULLTERM, SRVBIGCHAR, 8000, SRVBIGCHAR, 0, NULL);
srv_describe(pSrvProc, 4, "Comment", SRV_NULLTERM, SRVBIGCHAR, 8000, SRVBIGCHAR, 0, NULL);

/* Длину посылаемых данных и сами данные указываем каждый раз перед вызовом srv_sendrow*/
for(int k=0; k<3; k++)
{
srv_setcoldata(pSrvProc, 1, (void*)&Buff[k].id);

srv_setcollen(pSrvProc, 2, strlen(Buff[k].Name));
srv_setcoldata(pSrvProc, 2, Buff[k].Name);

srv_setcollen(pSrvProc, 3, strlen(Buff[k].FullName));
srv_setcoldata(pSrvProc, 3, Buff[k].FullName);

srv_setcollen(pSrvProc, 4, strlen(Buff[k].Comment));
srv_setcoldata(pSrvProc, 4, Buff[k].Comment);

srv_sendrow(pSrvProc);
}

/* Сообщаем о завершении передачи данных клиенту */
srv_senddone(pSrvProc, (SRV_DONE_COUNT | SRV_DONE_MORE), 0, 1);

Отладка расширенных хранимых процедур

Техника отладки расширенных хранимых процедур ни чем не отличается от отладки обычных DLL библиотек. Всё сказанное ниже относится к отладчику интегрированной среды разработки MS Visual Studio 6.0 Если Вы установили MS SQL Server 2000 на свой компьютер, сделайте следующие настройки:

1. Откройте панель управления службами windows и остановите сервис MS SQL Server.

2. Откройте проект расширенной хранимой процедуры и соберите его (F7).

3. Скопируйте dll библиотеку расширенной хранимой процедуры в каталог (C:Program FilesMicrosoft SQL ServerMssqlBinn).

4. Откройте диалоговое окно «Project Setings» (выполнив команды меню Project > Setings …) и перейдите на вкладку Debug. (см. рис. 1) В списке «Category» выберите «General». В поле «Executing for debug session» укажите путь к исполнимому файлу MS SQL Server 2000 (или выберите его щёлкнув на кнопке со стрелкой справа от поля ввода). По умолчанию (если Вы не меняли каталог во время установки MS SQL Server 2000) путь к файлу MS SQL Server 2000 - C:Program FilesMicrosoft SQL ServerMssqlBinn.

Щёлкните на кнопке , чтобы сохранить внесённые изменения.

5. Установите точку прерывания (F9) в исходном коде расширенной хранимой процедуры.

6. Выполните команду «GO» из меню «Build» > «Start Debug» (F5).
После запуска отладчика MS SQL Server 2000 будет выполняться в режиме обычного приложения, (а не сервиса Windows NT) при этом все диагностические сообщения будут выводится в консольном окне. (см. рис. 2)

7. Вызовите расширенную хранимую процедуру из клиентского приложения. Если предыдущие пункты выполнены правильно - произойдёт срабатывание установленной вами точки прерывания и отладчик MS Visual Studio 6.0 перейдёт в режим трассировки исходного кода.

Пишем расширенную хранимую процедуру xp_GetUserList

Вот мы и подошли к реализации нашего технического задания, о котором я говорил Вам в первой части статьи. Теперь мы знаем достаточно, чтобы справиться с этой задачей. Вкратце напомню её:

« Разработать расширенную хранимую процедуру для MS SQL Server 2000, которая получает полный список пользователей зарегистрированных в домене, и возвращает его клиенту в виде стандартного набора записей (record set). В качестве первого входного параметра функция получает имя сервера содержащего базу данных каталога (Active Directory), т.е имя контролера домена. Если этот параметр равен NULL, тогда необходимо передать клиенту список локальных групп. Второй параметр будет использоваться extended stored procedure для возврата значения – результата успешной/неуспешной работы (OUTPUT параметр). Если, расширенная хранимая процедура выполнена успешно, необходимо передать количество записей возвращённых в клиентский record set , если в процессе работы не удалось получить требуемую информацию, значение второго параметра необходимо установить в -1, как признак неуспешного завершения. »

Для получения списка доменных пользователей мы будем использовать только одну функцию Network Management API - NetQueryDisplayInformation. Эта функция предназначена для получения списка пользователей, групп, или компьютеров зарегистрированных в домене. Тема использования Network Management API выходит за рамки этой статьи, и поэтому я не буду подробно останавливаться на описании его возможностей. Здесь я предполагаю, что любознательный читатель сам заглянет в MSDN, чтобы поближе познакомиться со всеми возможностями, которые предоставляет Network Management API. Итак, пожалуй, можно начинать … )))

Перед использованием функции NetQueryDisplayInformation необходимо подключить к Вашему проекту заголовочный файл Lm.h и указать имя библиотечного файла NETAPI32.lib в опциях компоновщика. На этом подготовительный этап закончен. Теперь коротко рассмотрим саму функцию. Вот её прототип:

NET_API_STATUS NetQueryDisplayInformation(
LPCWSTR ServerName,
DWORD Level,
DWORD Index,
DWORD EntriesRequested,
DWORD PreferredMaximumLength,
LPDWORD ReturnedEntryCount,
PVOID* SortedBuffer
);

ServerName – указатель на строку, с именем удалённого сервера. Если этот параметр равен NULL, функция работает с локальным компьютером;
Level – определяет тип информации, которую перечисляет функция. Доступны следующие значения:

=1. Возвращает список учётных записей пользователей. Параметр SortedBuffer должен указывать на массив структур типа NET_DISPLAY_USER.
=2. Возвращает список учётных записей компьютеров. Параметр SortedBuffer должен указывать на массив структур типа NET_DISPLAY_MACHINE.
=3. Возвращает список учётных записей групп. Параметр SortedBuffer должен указывать на массив структур типа NET_DISPLAY_GROUP.

Для структуры NET_DISPLAY_USER, я приведу описание только основных полей :

typedef struct _NET_DISPLAY_USER {
LPWSTR usri1_name; //указатель на строку содержащую имя пользователя
LPWSTR usri1_comment; //указатель на строку комментария ассоциированную
DWORD usri1_flags; с данным пользователем
LPWSTR usri1_full_name; //указатель на строку содержащую полное имя
DWORD usri1_user_id; пользователя
DWORD usri1_next_index; //индекс последней записи возвращённой функцией
} NET_DISPLAY_USER, *PNET_DISPLAY_USER;

Index – определяет индекс первой записи, начиная с которой, функция будет выводить информацию. Укажите значение 0, для перечисления информации начиная с первой доступной записи;

EntriesRequested – задаёт максимальное число записей объектов, для которых функция возвращает информацию. В Windows 2000, а так же более поздних версиях, каждый вызов NetQueryDisplayInformation способен вернуть информацию не более чем о 100 записях объектов;

PreferredMaximumLength – определяет максимальное размер в байтах выделяемого функцией буфера на который указывает параметр SortedBuffer. Рекомендуется установить значение этого параметра равным константе MAX_PREFERRED_LENGTH;

ReturnedEntryCount – указатель на переменную, в которую функция заносит количество записей содержащихся в буфере, на который указывает параметр SortedBuffer. Если значение этого параметра, после вызова функции оказалось равно 0, значит указан слишком большой номер индекса, для которого нет доступных записей в базе SAM;

SortedBuffer – указатель на переменную, которая получает адрес буфера выделенного функцией. Буфер содержит список записей с информацией о запрашиваемых объектах. Формат этих данных зависти от значения параметра Level указанного при вызове функции. Так как этот буфер выделяет система, Вы должны освободить его используя функцию NetApiBufferFree. Вы должны освобождать буфер после каждого вызова NetQueryDisplayInformation, даже если она завершилась со значением ERROR_MORE_DATA.

Возвращаемые значения:
ERROR_ACCESS_DENIED – пользователь не имеет доступа к запрашиваемой информации;
ERROR_INVALID_LEVEL – не верно указан параметр Level;
ERROR_MORE_DATA – предыдущие записи возвращённые функцией не последние. Доступны ещё данные. Для получения следующей порции данных вызовите NetQueryDisplayInformation снова, установив параметр Index равным значению поля структуры next_index последней записи полученной от предыдущего вызова функции.
NERR_Success – успешный вызов, все данные получены.

Вот собственно и всё, что я хотел рассказать о функции NetQueryDisplayInformation.
Пример расширенной хранимой процедуры GetUserList Вы можете скачать здесь . Если у Вас возникли вопросы – буду рад возможности ответить на них. Пишите сюда: release@kuben.elektra.ru

А я снова прощаюсь с Вами – надеюсь, что не на долго…

Release.
PS:
Материал к статье в виде проекта качать здесь.

Hosted by uCoz